package community.peers.internetradio.network;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.MediaPlayer;
import android.support.v4.app.NotificationCompat;

import com.android.volley.RequestQueue;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import community.peers.internetradio.R;
import community.peers.internetradio.broadcastintent.PlayPause;
import community.peers.internetradio.broadcastintent.Stop;

public class Player {

    /**
     * Needed when we receive actions occuring in the notification
     * player (via BroadcastIntent). This identify the notification.
     */
    public static final int NOTIFICATION_ID = 0;

    /**
     * Cut ticker message to this length.
     * The reason is that if it's too long, the user will receive
     * and excessively long notification in the status bar.
     */
    public static final int TICKER_MAX_LENGTH = 100;

    protected static HashMap<String, String> radio;
    protected static MediaPlayer player;
    protected static Context context;

    /**
     * Timer to poll stream metadata every once in a while.
     * I don't know if Icecast supports push events, so I'm
     * sending a request for metadata every minute.
     */
    protected static Timer metadataTimer;

    /**
     * The notification that will be displayed in the notification drawer.
     */
    protected static NotificationCompat.Builder streamNotification;

    public static void playStream(
        Context theContext,
        HashMap<String, String> theRadio)
    {
        Player.stop();

        context = theContext;
        radio = theRadio;
        player = new MediaPlayer();

        try {
            player.setDataSource(radio.get("stream"));
            player.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
                @Override
                public void onBufferingUpdate(MediaPlayer mp, int percent) {
                }
            });
            player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                // Player ready, start automatically
                Player.play();
                }
            });
            player.prepareAsync();

            Player.notifyNewStream();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // Restart mediaplayer after pause
    public static void play()
    {
        if (player != null)
        {
            player.start();
            Player.fetchIcecastData();
        }
    }

    /**
     * Do not pause radio. "Pause" caches the stream, so when we play()
     * it again later, it will restart from where it was paused.
     */
    public static void pause()
    {
        if (player != null)
        {
            player.pause();
            Player.stopFetchingIcecastData();
        }
    }

    public static void playOrPause()
    {
        if (player != null)
            if (player.isPlaying())
            {
                Player.stopFetchingIcecastData();

                /**
                 * Stop and reset the MediaPlayer.
                 * The reason we reset() after stop() is because when
                 * prepareAsync() is called after stop(), the stream
                 * stops automatically after a few second. Looks like
                 * it empties the stream cache and then stops. I don't
                 * know why this is happening, so we just return to the
                 * Idle() state and re-initialize the data source again.
                 */
                player.stop();
                player.reset();

                Player.updateNotification("Paused", null, null);
            } else {
                try {
                    player.setDataSource(radio.get("stream"));
                    player.prepareAsync();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                Player.notifyNewStream();
            }
    }

    public static void stop()
    {
        if (player != null)
        {
            Player.stopFetchingIcecastData();

            player.stop();
            player.release();
            player = null;
        }
    }

    /**
     * Get Icecast data from the stream, if available.
     * The data from the stream has info about the current song.
     *
     * DEPRECATED because the FFmpegMediaMetadataRetriever dependency
     * is very large. The final apk was ~25MB.
     * Replace with method below
     */
    /*
    public static void fetchIcecastData()
    {
        // Stop any previous task fetching data
        Player.stopFetchingIcecastData();

        /**
         * When a timer is no longer needed, users should call cancel(),
         * which releases the timer's thread and other resources. Timers
         * not explicitly cancelled may hold resources indefinitely.
         * /
        Player.metadataTimer = new Timer();

        // Fetch data automatically every few minutes
        Player.metadataTimer.schedule(
            new TimerTask()
            {
                @Override
                public void run()
                {
                String songMetadata = "";

                try {
                    // Get the data
                    FFmpegMediaMetadataRetriever mdr = new FFmpegMediaMetadataRetriever();
                    mdr.setDataSource(Player.radio.get("stream"));
                    String icy = mdr.extractMetadata(FFmpegMediaMetadataRetriever.METADATA_KEY_ICY_METADATA);
                    mdr.release();

                    /**
                     * Parse Icecast info.
                     * Data comes as StreamTitle='';StreamUrl='';...
                     * so we need to parse it.
                     * /
                    Map<String, String> metadata = new HashMap();
                    String[] metaParts = icy.split(";");
                    Pattern p = Pattern.compile("^([a-zA-Z]+)=\\'(.*)\\'$");
                    Matcher m;
                    for (int i = 0; i < metaParts.length; i++)
                    {
                        m = p.matcher(metaParts[i]);
                        if (m.find())
                            metadata.put(m.group(1), m.group(2));
                    }

                    String streamTitle = metadata.get("StreamTitle").trim();

                    if (streamTitle != null && streamTitle.length() > 0)
                        songMetadata = streamTitle;
                } catch (Exception e) {}

                // Update notification with new metadata about current song
                if (songMetadata == "")
                    Player.updateNotification("", null, null);
                else
                    Player.updateNotification(songMetadata, "Listening " + songMetadata, null);
            }
        },
        // Start after 0s, repeat after 60s
        0, 60000);
    }
    */

    public static void fetchIcecastData()
    {
        // Stop any previous task fetching data
        Player.stopFetchingIcecastData();

        /**
         * When a timer is no longer needed, users should call cancel(),
         * which releases the timer's thread and other resources. Timers
         * not explicitly cancelled may hold resources indefinitely.
         */
        Player.metadataTimer = new Timer();

        // Fetch data automatically every few minutes
        Player.metadataTimer.schedule(
            new TimerTask()
            {
                @Override
                public void run()
                {
                    String songMetadata = "";

                    try {
                        Map<String, String> metadata =
                            Icecast.getMetadata(new URL(radio.get("stream")));

                        if (metadata != null)
                            songMetadata = metadata.get("StreamTitle").trim();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    // Update notification with new metadata about current song
                    if (songMetadata.length() == 0)
                        Player.updateNotification("", null, null);
                    else
                        Player.updateNotification(songMetadata, "Listening " + songMetadata, null);
                }
            },
            // Start after 0s, repeat after 60s
            0, 60000);
    }

    public static void stopFetchingIcecastData()
    {
        if (Player.metadataTimer != null)
        {
            Player.metadataTimer.cancel();
            Player.metadataTimer.purge();
            Player.metadataTimer = null;
        }
    }

    // Show a notification about the new stream
    protected static void notifyNewStream()
    {
        // Play/Pause intent when tapping the notification
        // PendingIntent.getBroadcast (Context context, int requestCode, Intent intent, int flags)
        Intent play_intent = new Intent(context, PlayPause.class);
        PendingIntent play_pintent = PendingIntent.getBroadcast(context, 0, play_intent, 0);

        // Stop intent when removing the notification
        Intent stop_intent = new Intent(context, Stop.class);
        PendingIntent stop_pintent = PendingIntent.getBroadcast(context, 0, stop_intent, 0);

        // Normalize radio name
        String radioName = radio.get("name").trim();
        if (radioName.length() > Player.TICKER_MAX_LENGTH)
            radioName = radioName.substring(0, Player.TICKER_MAX_LENGTH) + "...";

        Player.streamNotification = new NotificationCompat.Builder(context)
            .setAutoCancel(false)
            // Show controls on lock screen even when user hides sensitive content.
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .setSmallIcon(R.drawable.ic_graphic_eq)
            //.setLargeIcon()
            // Add media control buttons that invoke intents in your media service
            //.addAction(R.drawable.ic_play, "Play/Pause", play_pintent)
            //.addAction(R.drawable.ic_stop, "Stop", stop_pintent)
            .setContentIntent(play_pintent)
            .setDeleteIntent(stop_pintent)
            //.addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)  // #1
            //.addAction(R.drawable.ic_next, "Next", nextPendingIntent)     // #2
            // Apply the media style template
            //.setStyle(new NotificationCompat.MediaStyle()
            //        .setShowActionsInCompactView(1 /* #1: pause button */)
            //        .setMediaSession(mMediaSession.getSessionToken()))
            .setContentTitle(radioName)
            .setContentText("Buffering...")
            //.setLargeIcon(albumArtBitmap)
            .setTicker("Buffering... " + radioName)
            // Display datetime in notification
            .setShowWhen(true)
            // Show elapsed time instead of creation time
            .setUsesChronometer(true);

        // Sticky notification
        //theNotification.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR

        // Gets an instance of the NotificationManager service
        NotificationManager mNotifyMgr =
            (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);

        // Builds the notification and issues it.
        mNotifyMgr.notify (
            Player.NOTIFICATION_ID,
            Player.streamNotification.build());

        updateNotificationLargeIcon();
    }

    // Change text when new Icecast data is available
    protected static void updateNotification(String content, String ticker, Bitmap largeIcon)
    {
        // Content is changed to update song info
        if (content != null)
            Player.streamNotification.setContentText(content);

        // Show a new ticker
        if (ticker != null)
        {
            // Remove leading/trailing spaces
            ticker = ticker.trim();

            // Cut ticker message if it's too long
            if (ticker.length() > Player.TICKER_MAX_LENGTH)
                ticker = ticker.substring(0, Player.TICKER_MAX_LENGTH) + "...";

            Player.streamNotification.setTicker(ticker);
        }

        // Update large icon in notification
        if (largeIcon != null)
            Player.streamNotification.setLargeIcon(largeIcon);

        // Gets an instance of the NotificationManager service
        NotificationManager mNotifyMgr =
                (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);

        // Builds the notification and issues it.
        // Because the ID remains unchanged, the existing notification is updated
        mNotifyMgr.notify (
            Player.NOTIFICATION_ID,
            Player.streamNotification.build());
    }

    /**
     * Send a new async HTTP request to fetch radio image, and then
     * upload the notification with the new logo.
     */
    protected static void updateNotificationLargeIcon()
    {
        RequestQueue queue = Volley.newRequestQueue(context);
        ImageLoader imageLoader = new ImageLoader(queue, new ImageLoader.ImageCache() {
            @Override
            public Bitmap getBitmap(String url) {
                return null;
            }

            @Override
            public void putBitmap(String url, Bitmap bitmap) {

            }
        });

        imageLoader.get(Player.radio.get("logo"), new ImageLoader.ImageListener() {
            @Override
            public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
                Player.updateNotification(null, null, response.getBitmap());
            }

            @Override
            public void onErrorResponse(VolleyError error) {

            }
        });
    }
}